package cn.tannn.ops.nginx.controller;

import cn.hutool.core.thread.ThreadUtil;
import cn.tannn.jdevelops.annotations.web.mapping.PathRestController;
import cn.tannn.jdevelops.exception.built.BusinessException;
import cn.tannn.jdevelops.jpa.result.JpaPageResult;
import cn.tannn.jdevelops.result.response.ResultPageVO;
import cn.tannn.jdevelops.result.response.ResultVO;
import cn.tannn.jdevelops.utils.http.ResponseFile;
import cn.tannn.ops.common.message.MessageSsl;
import cn.tannn.ops.message.service.WebHookService;
import cn.tannn.ops.nginx.controller.dto.DomainPage;
import cn.tannn.ops.nginx.controller.dto.DomainRegister;
import cn.tannn.ops.nginx.controller.dto.SettingSslPath;
import cn.tannn.ops.nginx.entity.Domains;
import cn.tannn.ops.nginx.service.CloudAccessKeyService;
import cn.tannn.ops.nginx.service.DomainsService;
import cn.tannn.ops.ssh.controller.SshInfoController;
import cn.tannn.ops.ssh.service.SshInfoService;
import cn.tannn.ops.util.*;
import com.jcraft.jsch.Session;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import java.io.File;
import java.util.zip.ZipOutputStream;

/**
 * @author <a href="https://t.tannn.cn/">tan</a>
 * @version V1.0
 * @date 2024/8/22 下午2:29
 */
@PathRestController("/nginx/ssl")
@Tag(name = "ssl管理")
@Slf4j
@RequiredArgsConstructor
public class NginxSslController {

    private final DomainsService domainsService;
    private final WebHookService webHookService;
    private final CloudAccessKeyService cloudAccessKeyService;
    private final SshInfoService sshInfoService;
    private final SshInfoController sshInfo;

    @Operation(summary = "域名证书校验", description = "不要写https://")
    @GetMapping("verify")
    public ResultVO<String> sslVerify(@RequestParam("domain") String domain) throws Exception {
        Domains domains = NginxUtil.domainSslInfo(domain);
        domainsService.findOnly("domain", domains.getDomain()).ifPresent(exist -> {
            domains.setSslPath(exist.getSslPath());
            domains.setId(exist.getId());
            domains.setSshId(exist.getSshId());
            domains.setWebhookId(exist.getWebhookId());
            domains.setAccessId(exist.getAccessId());
            domainsService.saveOne(domains);
        });
        if (domains.isEfficacious()) {
            return ResultVO.successMessage("域名证书正常");
        } else {
            return ResultVO.failMessage("域名证书过期");
        }
    }

    @Operation(summary = "注册需要监控的域名", description = "不要写https://")
    @PostMapping("registerDomain")
    public ResultVO<Domains> registerDomain(@RequestBody @Valid DomainRegister register) throws Exception {
        domainsService.findOnly("domain", register.getDomain().trim()).ifPresent(x -> {
            throw new BusinessException("请勿重复注册[" + register.getDomain() + "]");
        });
        domainsService.saveOne(register.toDomains());
        return ResultVO.successMessage("注册成功");
    }

    @PostMapping("pageDomain")
    @Operation(summary = "分页查询域名")
    public ResultPageVO<Domains, JpaPageResult<Domains>> pageDomain(@RequestBody @Valid DomainPage page) {
        Page<Domains> roles = domainsService.findPage(page, page.getPage());
        return ResultPageVO.success(JpaPageResult.toPage(roles), "查询成功");
    }

    @Operation(summary = "删除域名")
    @GetMapping("deleteDomain")
    @Parameter(name = "id", description = "id", required = true)
    public ResultVO<String> deleteDomain(@RequestParam("id") Long id) {
        domainsService.deleteEq("id", id);
        return ResultVO.success();
    }

    @Operation(summary = "域名设置webhook")
    @GetMapping("webhook")
    @Parameter(name = "id", description = "id", required = true)
    @Parameter(name = "webhookId", description = "为空表示取消", required = true)
    public ResultVO<String> webhook(@RequestParam("id") Long id,
                                    @RequestParam(value = "webhookId", required = false) Long webhookId) {
        domainsService.findOnly("id", id).ifPresent(exit -> {
            if (webhookId != null) {
                webHookService.findOnly("id", webhookId).orElseThrow(() -> new BusinessException("请选择正确的webhook"));
                exit.setWebhookId(webhookId);
            } else {
                exit.setWebhookId(null);
            }
            domainsService.saveOne(exit);
        });
        return ResultVO.successMessage("设置成功");
    }

    @Operation(summary = "域名设置Access")
    @GetMapping("access")
    @Parameter(name = "id", description = "id", required = true)
    @Parameter(name = "accessId", description = "为空表示取消", required = true)
    public ResultVO<String> access(@RequestParam("id") Long id,
                                   @RequestParam(value = "accessId", required = false) String accessId) {
        domainsService.findOnly("id", id).ifPresent(exit -> {
            if (accessId != null) {
                cloudAccessKeyService.findOnly("accessId", accessId)
                        .orElseThrow(() -> new BusinessException("请选择正确的access"));
                exit.setAccessId(accessId);
            } else {
                exit.setAccessId(null);
            }
            domainsService.saveOne(exit);
        });
        return ResultVO.successMessage("设置成功");
    }


    @Operation(summary = "域名设置ssh")
    @GetMapping("ssh")
    @Parameter(name = "id", description = "id", required = true)
    @Parameter(name = "sshId", description = "为空表示取消", required = true)
    public ResultVO<String> ssh(@RequestParam("id") Long id,
                                @RequestParam(value = "sshId", required = false) Long sshId) {
        domainsService.findOnly("id", id).ifPresent(exit -> {
            if (sshId != null) {
                sshInfoService.findOnly("id", sshId)
                        .orElseThrow(() -> new BusinessException("请选择正确的SSH"));
                exit.setSshId(sshId);
            } else {
                exit.setSshId(null);
            }
            domainsService.saveOne(exit);
        });
        return ResultVO.successMessage("设置成功");
    }


    @Operation(summary = "域名设置sslPath")
    @PostMapping("sslPath")
    public ResultVO<String> ssh(@RequestBody @Valid SettingSslPath path) {
        domainsService.findOnly("id", path.getId()).ifPresent(exit -> {
            if (StringUtils.isNotBlank(path.getSslPath())) {
                exit.setSslPath(path.getSslPath());
            } else {
                exit.setSslPath(null);
            }
            domainsService.saveOne(exit);
        });
        return ResultVO.successMessage("设置成功");
    }


    @Operation(summary = "通知用户域名状态")
    @GetMapping("/webhook/notification")
    @Parameter(name = "id", description = "id", required = true)
    public ResultVO<String> webhookNotification(@RequestParam("id") Long id) throws Exception {
        domainsService.findOnly("id", id).ifPresent(exit -> {
            if (exit.getWebhookId() != null) {
                webHookService.findOnly("id", exit.getWebhookId())
                        .ifPresentOrElse(hook -> {
                            WebHookUtil.sendMessage(hook, new MessageSsl(exit));
                        }, () -> {
                            throw new BusinessException("webhook可能被删除请确定后重试");
                        });
            } else {
                throw new BusinessException("请先设置webhook");
            }
        });
        return ResultVO.successMessage("成功通知用户域名状态");
    }

    /**
     * todo 证书最好做缓存，目前是生成完成之后删除/替换了的没有做记录，所以每次都会重新取生成，会导致重复多次证书颁发方把你关小黑屋一周
     */
    @Operation(summary = "域名证书下载")
    @GetMapping("/download")
    @Parameter(name = "id", description = "id", required = true)
    public void downloadSsl(@RequestParam("id") Long id, HttpServletResponse response) throws Exception {
        domainsService.findOnly("id", id).ifPresent(exit -> {
//            if(exit.getExpiryDay()>10){
//                throw new BusinessException("域名证书有效期还很长请勿重写生成");
//            }
            if (StringUtils.isBlank(exit.getAccessId())) {
                throw new BusinessException("请先设置Access");
            }
            cloudAccessKeyService.findOnly("accessId", exit.getAccessId()).ifPresent(ac -> {
                try {
                    AcmeUtil.run(exit.getDomain(), ac);
                    // {@link AcmeUtil#loadOrCreateDomainKeyPair}
                    File keyFile = new File(exit.getDomain() + ".key");
                    // {@link AcmeUtil#fetchCertificate}
                    File pemFile = new File(exit.getDomain() + ".pem");
                    HttpServletResponse downResponse = ResponseFile.zipResponse(response, exit.getDomain());
                    try (ZipOutputStream zipOut = new ZipOutputStream(downResponse.getOutputStream())) {
                        // Add key file to zip
                        FileUtil.addToZipFile(keyFile, zipOut);
                        // Add pem file to zip
                        FileUtil.addToZipFile(pemFile, zipOut);
                    } catch (Exception e) {
                        throw new BusinessException("域名证书下载失败 domian:  "+exit.getDomain()+",error: "+e.getMessage(), e);
                    }finally {
                        cn.hutool.core.io.FileUtil.del(keyFile);
                        cn.hutool.core.io.FileUtil.del(pemFile);
                    }
                } catch (Exception e) {
                    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                    throw new BusinessException("域名证书下载时生成失败,error: "+e.getMessage(), e);
                }
            });
        });
    }

    /**
     * todo 证书最好做缓存，目前是生成完成之后删除/替换了的没有做记录，所以每次都会重新取生成，会导致重复多次证书颁发方把你关小黑屋一周
     */
    @Operation(summary = "域名证书部署")
    @GetMapping("/build")
    @Parameter(name = "id", description = "id", required = true)
    public ResultVO<String> buildSsl(@RequestParam("id") Long id, HttpServletResponse response) throws Exception {
        domainsService.findOnly("id", id).ifPresent(exit -> {
            if (exit.getSshId() == null) {
                throw new BusinessException("请先设置SSH");
            }
            if (StringUtils.isBlank(exit.getSslPath())) {
                throw new BusinessException("请先设置SslPath");
            }
            if (StringUtils.isBlank(exit.getAccessId())) {
                throw new BusinessException("请先设置Access");
            }
            cloudAccessKeyService.findOnly("accessId", exit.getAccessId()).ifPresent(ac -> {
                ThreadUtil.execAsync(() ->{
                    try {
                        AcmeUtil.run(exit.getDomain(), ac);
                        // {@link AcmeUtil#loadOrCreateDomainKeyPair}
                        File keyFile = new File(exit.getDomain() + ".key");
                        // {@link AcmeUtil#fetchCertificate}
                        File pemFile = new File(exit.getDomain() + ".pem");
                        sshInfoService.findOnly("id", exit.getSshId()).ifPresent( ssh -> {
                            Session session = null;
                            try {
                                session = SSHUtil.connectionServer(ssh.getUsername()
                                        , ssh.getPassword()
                                        , ssh.getUrl()
                                        , ssh.getPort());
                                SSHUtil.replaceFile(session,exit.pemPath(),pemFile.getPath());
                                SSHUtil.replaceFile(session,exit.keyPath(),keyFile.getPath());
                            }catch (Exception e){
                                SSHUtil.disconnectServer(session);
                            }finally {
                                cn.hutool.core.io.FileUtil.del(keyFile);
                                cn.hutool.core.io.FileUtil.del(pemFile);
                            }
                        });
                    } catch (Exception e) {
                        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                        throw new BusinessException("域名证书下载时生成失败,error: "+e.getMessage(), e);
                    }
                });
            });
        });
        return ResultVO.successMessage("部署成功,请手动重启nginx");
    }

}
